Contents

参考:《改善java程序的151个建议》

1.三元操作符的类型尽量保持一致,如果不一致会发生类型转换。

2.避免带有变长参数的方法重载。

  1. 如果重载了带有变长参数的方法,不要隐藏实参类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Client {

    public void methodA(String str, Integer...is){

    }

    public void methodA(String str, String...strs){

    }
    public static void main(String[] args){
    Client client = new Client();
    client.methodA(“China”, 0);
    client.methodA(“China”, “People”);
    client.methodA(“China”, null);//这样就不知道调用哪个方法了

    String[] strs = null;
    client.methodA("China", strs);//没有隐藏实参,且这样就能知道调用哪个方法了
    }
    }

4.子类重写父类含变长参数(例如int…)的方法时,加@Override,并且子类方法不要用int[]来代替父类的变长参数,老老实实写一样的参数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Base{
void fun(int price, int...bs){
System.out.println("Base...fun");
}
}

class Sub extends Base{
@Override
void fun(int price, int...ss){
System.out.println("Sub...fun");
}

}

public class lian {

public static void main(String[] args){

Base base = new Sub();
base.fun(1, 1);

}
}
输出:
Sub...fun

Base对象把子类对象Sub做了向上转型,再由子类调用fun方法

5.注意couns++就等效于 count = count + 1; 不要写成了count = count++;

6.尽量少用静态导入
静态导入遵循两个规则:
不使用*,除非是导入静态常量类(只包含常量的类或接口)
方法名是具有明确、清晰表象意义的工具类

7.尽量在case语句后面都加break,这里已经有无数人犯过错了,即使为了一些方便可能几种情况都对于相同的处理而省略掉个别break,但也尽量别偷懒,把break写上吧。

一种更简单的方法是:在Eclipse中 Perfermaces->java->Compiler->Errors/Warnings->Potential Programming problems 中修改’switch’ case fall-through为Error级别,只要case语句后面没有break,直接报错

8.易变业务使用脚本语言编写

9.序列化类中,不要使用构造函数为final变量赋值。因为反序列化的时候不会执行构造函数。

10.反序列化时final变量在以下情况下不会被重新赋值:
通过构造函数为final变量赋值,反序列化时不会执行构造函数
通过方法返回值为final变量赋值
Final修饰的属性不是基本属性。

11.用偶判断,不用奇判断: i % 2 == 0 ? “偶数” : “奇数”

  个人理解:用位运算更干脆,即(i & 1) == 0? “偶数” : “奇数”;
对于正数,零,在计算机里是二进制存储,偶数最后一位一定为0,奇数最后一位一定为1
对于负数,在计算机里是存补码,即反码+1,奇数最后一位为1,反码后为0,再+1,一定为1,所以无影响,还是可以用i&1判断,偶数也是一样的

12.不要让类型默默转换 , 基本类型转换时,使用主动声明方式减少不必要的Bug
例如:long a = 30 10000 1000 60 8; //3亿 60 8
输出: -2028888064
因为java是先运算然后再进行类型转换的。
解决方法: long a = 1L *…;

13.注意边界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
例如: public final static int LIMIT = 2000;

public static void main(String[] main){

int cur = 1000;
Scanner sc = new Scanner(System.in);
System.out.println("请输入需要预定的数量: ");
while(sc.hasNext()){
int order = sc.nextInt();
if(order > 0 && order + cur <= LIMIT){
System.out.println("你已经成功预定了 " + order + "个产品");
}else{
System.out.println("超界了,预定失败");
}
}
}

分析: 因为2147483647 + 1000 = -2147482649 < 2000,所以边界测试很有必要。
  如果一个方法接受的是int类型的参数,那一下三个值是必测的:0,最大正数,最小负数。
改:if(order > 0 && order <= 1000 && order + cur <= LIMIT)

14.包装类型参与运算时,要做null值校验。

1
2
3
4
5
6
7
8
9
10
11
12
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(null);

int count = 0;
for(Integer i : list){
count += i;
}
System.out.println(count);
报错:Exception in thread "main" java.lang.NullPointerException
at yu.Lian4.main(Lian4.java:19)

分析: 拆箱时通过intValue()方法来实现,结果到null时报空了,null无法转换为基本类型
解决方法:count += (i != null ) ? i : 0;

15.包装类型比较大小问题:
Java中 == 对于基本类型则判断值是否相等,如果是对象则判断地址是否相等, >,<只能是数字类型的判断,对于Integer包装类型,可以用intValue()返回值进行比较,其实还可以利用实例的compareTo()方法

16.优先使用整型池:
  装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
valueOf()方法返回的是对象,而xxxValue返回的是对应的基本类型。
对于Integer优先使用valueOf来生成包装对象,因为这里有优化,整型池。

17.接口中不要放实现代码。

18.静态变量一定要先声明后赋值。下面来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Lian4 {

static{
i = 100;
}

public static int i = 1;

public static void main(String[] main){

System.out.println(i);

}
}
输出:
1

分析:
  静态变量是类加载时被分配到数据区的,他在内存中只有一个拷贝,不会被分配多次,其后的所有赋值操作都是值改变,地址则保持不变。 我们知道JVM初始化变量时先声明空间,然后赋值的,也就是说:在JVM中是分开执行,等价于:
int i ;//分配地址空间
i = 100; // 赋值
静态变量是类初始化时首先被加载的,JVM会去查找类中所有的静态声明,然后分配空间,注意这时候只是完成了地址空间的分配,还没有赋值,之后JVM会根据类中静态赋值(包括静态类赋值和静态块赋值)的先后顺序来执行。
对于这个程序来说,就是先声明了int类型的地址空间,并把地址传递给了i,然后按照类中的先后顺序执行赋值动作,首先执行静态块中的i=100,然后执行i=1,那最后的结果当然就是1了
所以遵循java通用的开发规范“变量先声明后使用”是一个良好的编码风格。

19.构造函数尽量简化,尽量不要对其他的类有依赖性 ,例如父类是抽象类,构造函数里有一个变量赋值却依赖于一个抽象方法,而抽象方法由子类的实现,这样容易出错。

20.避免在构造函数中初始化其他类。

21.如果一个类不允许实例化,就要保证“平常”渠道都不能实例化他。
例如:

1
2
3
public final class Math{
private Math() {}
}

这样就无法“平常”的实例化他了,但如果通过反射来生成Math实例就无法保证Math实例的正确性了,可以像下面这样:

1
2
3
4
5
public final class Math{
private Math() {
throw new Error(“不要实例化我!”);
}
}

如此才能保证一个工具类不会实例化,并且保证所有的访问都是通过类名来访问的,不过如果像上面这样做了,此工具类就不要做继承的打算了,因为子类实例化要调用父类的构造函数,那么问题就出现了。

22.在重写equals()中使用getClass进行类型判断,别用instanceof

23.推荐覆写toString()方法,默认的toString()方法不友好,如果是自己的类,则打印出的是类名+@+hashCode,不是给人看的,是给机器看的。
例如可以这样:

1
2
3
4
5
@Override
Public String toString()
{

return String.format(“%s.name=%s”, this.getClass(), this.paraes);
}

补:当Bean的属性较多时,自己实现就不可取了,不过可以使用apache的commons工具包中的ToStringBuilder类,简洁,实用又方便

  1. 推荐使用String直接量赋值

对于String类,不建议使用new String(“a”)的方法来赋值,而是类似String str = “asd”来直接赋值。原因是java为了避免在一个系统中大量产生String对象(因为String是经常使用的类型),于是设计了字符串常量池。在其中容纳的都是String对象,他的创建机制是:
创建一个字符串时,首先检查池中是否有字面值相等的字符串,如果有,则不再创建,直接返回池中该对象的引用,若没有则创建String对象,然后放到池中,并返回新建对象的引用。
而直接声明一个String对象(new String(“a”))是不检查字符串常量池的,也不会把对象放到池中。
请记住String对象是不可变的。任何对他的change都会产生新的对象返回,而原对象不变。

25.注意String的两个方法的参数:

1
2
3
4
5
6
 String
replace(CharSequence target, CharSequence replacement)
使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String
replaceAll(String regex, String replacement)
使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

Replace的第一个参数是要被换的字符,第二个参数是替换他的字符
replaceAll的第一个参数是正则表达式,第二个参数是待匹配的字符串

26.使用String类的场景:
在字符串不经常变化的场景中可以使用String类,例如常量的声明,少了的变量运算
使用StringBuffer类的场景:
在频繁进行字符串的运算,并且运行在多线程的环境中,则可以考虑使用StringBuffer,例如:XML解析、HTTP参数解析和封装等。
使用StringBuilder类的场景:
在频繁进行字符串的运算,并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。

Contents